import {AfterContentInit, Component, ElementRef, OnDestroy, OnInit, Renderer2, TemplateRef, ViewChild} from '@angular/core';
import {FurtherData, Tab} from '../../../public/setting/model';
import {NzDropdownContextComponent, NzDropdownService, NzMessageService, NzModalService} from 'ng-zorro-antd';
import {IdeService} from '../ide.service';
import {Setting} from '../../../public/setting/setting';
import {SaveConfirmComponent} from '../save-confirm/save-confirm.component';
// ace editor
import 'brace';
import 'brace/mode/python';
import 'brace/mode/html';
import 'brace/mode/css';
import 'brace/mode/javascript';
import 'brace/mode/json';
import 'brace/theme/xcode';
import 'brace/ext/language_tools';
import 'brace/snippets/python';
import 'brace/snippets/html';
import 'brace/snippets/css';
import 'brace/snippets/javascript';
import 'brace/snippets/json';
import {CodeType} from '../../../public/setting/enums';
import {TerminalComponent} from '../terminal/terminal.component';
import {AceComponent} from 'ngx-ace-wrapper';
import {MenuTreeComponent} from '../menu-tree/menu-tree.component';

@Component({
  selector: 'app-ide',
  templateUrl: './ide.component.html',
  styleUrls: ['./ide.component.scss']
})

export class IdeComponent implements OnInit, OnDestroy, AfterContentInit {
  public code: any;
  public app = Setting.APP;
  public furtherData: FurtherData;
  public curFilePath: string;
  public curFileType: string;

  public editorTabs: Array<Tab> = []; // editor tabs
  public codeTypes = CodeType; // the file code type
  public isShowMenu = true;
  public isShowExamples = false;
  public isShowRight = true;
  public bindDevice: any;
  public isRunningCode = false; // Whether the code is running
  public addMenu = ''; // Add menu
  public isResizeXterm: number;
  public selectedEditorTabIndex = 0; // current actived tab index
  public logSocket: WebSocket;
  public documentEventListener: any;
  public idePanels = {
    barTop: 0,
    barLeft: 0,
    barRight: 0,
    primary: 100,
    panelMiddle: 100,
    topParent: 100,
    panelLeft: 18,
    panelCenter: 52,
    panelRight: 30,
  };

  // ace editor config options
  public aceConfig: any = {
    mode: 'python',
    theme: 'xcode',
    showPrintMargin: false,
    config: {
      autoScrollEditorIntoView: true,
      copyWithEmptySelection: true,
      mergeUndoDeltas: 'always',
      fontSize: 16,
      wrap: true,
      scrollPastEnd: true,
      readOnly: false
    }
  };
  public deviceData: any = {};
  public selectedLine: string;
  public contextMenuTemplate: any;
  public hasBindDevice: boolean = false;

  private dropdown: NzDropdownContextComponent;
  private curContextTab: any; // current tab data when click the right mouse button
  private mouseupListener: any;
  private editorSaveListener: any; // Editor shortcuts save event listeners
  private contextMenuListener: any; // Editor right-click menu event listener
  private editorMouseWheelListener: any; // Mouse scroll quickly to enlarge the editor font listening
  private messageLisener: any; // listen iframe message
  private logoutButtonListener: any;

  @ViewChild(AceComponent) aceEditor: AceComponent;
  @ViewChild('blocklyHome') blocklyHome: ElementRef;
  @ViewChild('terminal') terminal: TerminalComponent;
  @ViewChild('menu') menuTreeComponent: MenuTreeComponent;

  constructor(private el: ElementRef,
              private renderer2: Renderer2,
              private modalService: NzModalService,
              private message: NzMessageService,
              private nzDropdownService: NzDropdownService,
              private ideService: IdeService) {
    this.hasBindDevice = Boolean(localStorage.getItem(Setting.storage.deviceData));
    if(this.hasBindDevice) this.deviceData = JSON.parse(localStorage.getItem(Setting.storage.deviceData));
  }

  ngOnInit() {
    this.stopF5();
    this.stopCtrlS();
  }

  ngAfterContentInit() {
    this.mouseEvent();
    this.listenMouseWheel();
    this.listenMouseUp();
    this.getMsgFormIframe();
  }

  getMsgFormIframe() {
    this.messageLisener = this.renderer2.listen('window', 'message', (event: any) => {
      if (event && event.data.hasOwnProperty('save')) {
        const curTab = this.editorTabs[this.selectedEditorTabIndex];
        curTab.code = event.data.xml;
        curTab.pythonCode = event.data.code;
        curTab.isValueChange = curTab.code !== curTab.historyCode;
        this.onValueChanged(event.data.xml, curTab);
        if (event.data.save === true) {
          this.saveFile(curTab);
        }
      }
    });
  }

  afterBindDevice($event) {
    this.deviceData = $event.deviceData;
  }

  getFurtherData($event) {
    this.furtherData = $event.data;
  }

  /**
   * Autocompletion
   * @param event
   * @param tab
   */
  makeAutocompletion(event, tab) {
    if (event && event.length > 1) {
      event[1].setOptions({
        enableBasicAutocompletion: true,
        enableSnippets: true,
        enableLiveAutocompletion: true
      });
    }
    this.listenSave(tab);
    this.listenContextMenu();
  }

  /**
   * when value change
   */
  onValueChanged(event, tab: Tab) {
    if (tab.saveTimer) {
      clearInterval(tab.saveTimer);
      tab.saveTimer = undefined;
    }
    tab.isValueChange = event !== tab.historyCode;
    tab.saveTimer = setTimeout(_ => {
      if (tab.isValueChange) {
        this.saveFile(tab, false);
      }
    }, 3000);
  }

  /**
   * on select code in editor, get selected line numbers
   * @param event
   * @param contextMenuTemplate
   */
  onSelectionChange(event: any[], contextMenuTemplate) {
    if (!this.furtherData.canEdit) {
      return;
    }
    this.contextMenuTemplate = contextMenuTemplate;
    let start: number, end: number;
    if (event && event.length === 2) {
      start = event[1].anchor.row;
      end = event[1].lead.row;
      this.selectedLine = `${start},${end + 1}`;
      if (start > end) {
        this.selectedLine = `${end},${start + 1}`;
      }
      if (event[1].anchor.row === event[1].lead.row && event[1].anchor.column === event[1].lead.column) {
        this.selectedLine = undefined;
      }
    }
  }

  /**
   * Open the file editor when you click the menu
   * Determine if it has been opened before, if so, find it and open it, if not open new tab
   */
  openEditor(data: any) {
    if (data) {
      this.curFilePath = data.path;
      this.curFileType = data.codeType;
    }
    if (data.codeType === CodeType.blocklyPython3 && data.content) {
      localStorage.setItem(data.path, data.content); // if it's blockly file，cache code to storage the the iframe page will catch it
    }
    if (this.editorTabs.some(item => item.path === data.path)) {
      this.selectedEditorTabIndex = this.editorTabs.findIndex(item => item.path === data.path);
    } else {
      this.getTabFileAndCreateTab(data, () => {
        this.selectedEditorTabIndex = this.editorTabs.length - 1;
        this.saveEditorTabs();
      });
    }
  }

  /**
   * get data and create new tab
   * @param data
   * @param callback
   */
  getTabFileAndCreateTab(data, callback?) {
    this.editorTabs.push({
      title: data.title,
      id: data.id,
      path: data.path,
      key: data.key,
      code: data.content || '',
      historyCode: data.content || '',
      codeType: data.codeType,
      isValueChange: false,
      closeable: true
    });
    if (data.codeType === CodeType.blocklyPython3 && data.content) {
      localStorage.setItem(data.path, data.content);
    } else {
      localStorage.removeItem(data.path);
    }
    if (typeof callback === 'function') {
      callback();
    }
  }

  /**
   * The following situations need to be saved:
   * 1.one menu be opened & added in tabs
   * 2.one menu be deleted & removed from tabs, another tab actived
   * 3.one menu be renamed & tab changed
   * 4.switch from tabs n the tabindex change
   * 5.tabs are closed
   */
  saveEditorTabs() {
    localStorage.setItem(Setting.storage.editorTabs, JSON.stringify(this.editorTabs)); // save all opened tabs
    localStorage.setItem(Setting.storage.selectedEditorTabIndex, String(this.selectedEditorTabIndex)); // save cur actived tab index
    localStorage.setItem(Setting.storage.curActivedMenu, JSON.stringify(this.editorTabs[this.selectedEditorTabIndex])); // save cur activing tab
  }

  /**
   * change editor fontSize
   * @param type
   */
  changeEditorFont(type) {
    if (type) {
      if (this.aceConfig.config.fontSize < 36) {
        this.aceConfig.config.fontSize += 2;
      }
    } else {
      if (this.aceConfig.config.fontSize > 12) {
        this.aceConfig.config.fontSize -= 2;
      }
    }
  }

  /**
   * get cur active tab when tab index change
   * @param idx
   */
  getCurEditor(idx) {
    if (this.editorTabs.length > 0) {
      this.curFilePath = this.editorTabs[idx].path;
      this.saveEditorTabs(); // save tab to storage
    }
  }

  /**
   * close tab
   * @param tab
   */
  closeTab(tab: Tab) {
    if (tab.isValueChange) {
      const modal = this.modalService.create({
        nzTitle: null,
        nzContent: SaveConfirmComponent,
        nzComponentParams: {
          title: `${Setting.I18nData['Ide0060']} <span class="color-theme">${tab.title}</span> ${Setting.I18nData['Ide0061']}`,
          descriptions: `${Setting.I18nData['Ide0062']}`,
        },
        nzWidth: 460,
        nzClassName: 'my-confirm-modal',
        nzFooter: [
          {
            label: ` ${Setting.I18nData['Ide0063']} `, // do not save
            shape: 'danger',
            onClick: () => {
              modal.destroy();
              this.editorTabs = this.editorTabs.filter(item => item.path !== tab.path);
            }
          },
          {
            label: ` ${Setting.I18nData['Ide0064']} `, // cancel
            type: 'default',
            onClick: () => {
              modal.destroy();
            }
          },
          {
            label: ` ${Setting.I18nData['Ide0065']} `, // save
            type: 'primary',
            onClick: () => {
              this.saveFile(tab);
              modal.destroy();
              this.editorTabs = this.editorTabs.filter(item => item.path !== tab.path);
            }
          },
        ],
      });
    } else {
      this.editorTabs = this.editorTabs.filter(item => item.path !== tab.path);
    }
    this.saveEditorTabs(); // Save the currently open tabs
  }

  /**
   * Close other tabs
   * @param tab
   */
  closeOthersTab(tab) {
    const closeTabs = this.editorTabs.filter(item => item.title !== tab.title);
    closeTabs.forEach(item => {
      this.closeTab(item);
    });
  }

  /**
   * Close all tabs
   */
  closeAllTabs() {
    this.editorTabs.forEach(item => {
      this.closeTab(item);
    });
  }

  /**
   * context menu to close tab
   * @param type
   */
  contextCloseTab(type) {
    if (this.curContextTab) {
      switch (type) {
        case 'cur':
          this.closeTab(this.curContextTab);
          break;
        case 'others':
          this.closeOthersTab(this.curContextTab);
          break;
        case 'all':
          this.closeAllTabs();
          break;
      }
    }
    this.dropdown.close();
  }

  /**
   * context menu
   * @param $event
   * @param template
   * @param tabData
   */
  contextMenu($event: MouseEvent, template: TemplateRef<void>, tabData: Tab): void {
    this.dropdown = this.nzDropdownService.create($event, template);
    this.curContextTab = tabData;
  }

  /**
   * toggle menu
   */
  toggleMenu() {
    this.isShowMenu = !this.isShowMenu;
    this.getPanelCenterSize(); // get the center panel size
    setTimeout(() => {
      this.resizeEditor();
    });
  }

  /**
   * toggle right panel
   */
  toggleRight() {
    this.isShowRight = !this.isShowRight;
    this.getPanelCenterSize(); // get the center panel size
    const me = this;
    setTimeout(function () {
      me.resizeEditor();
    });
  }

  /**
   * get the center panel size when left & right change
   */
  getPanelCenterSize() {
    if (this.isShowMenu && this.isShowRight) {
      this.idePanels.panelCenter = 100 - this.idePanels.panelLeft - this.idePanels.panelRight;
    } else if (!this.isShowMenu && this.isShowRight) {
      this.idePanels.panelCenter = 100 - this.idePanels.panelRight;
    } else if (!this.isShowMenu && !this.isShowRight) {
      this.idePanels.panelCenter = 100;
    }
  }

  /**
   * ctrl & mouse wheel change editor font
   */
  listenMouseWheel() {
    const me = this;
    if (me.editorMouseWheelListener) {
      me.editorMouseWheelListener();
    }
    this.editorMouseWheelListener = this.renderer2.listen('document', 'mousewheel', function (event: any) {
      if (event.wheelDelta && event.ctrlKey) {
        event.returnValue = false;
        if (event.target.className === 'ace_content') {
          me.changeEditorFont(event.wheelDelta > 0);
        }
      } else if (event.detail) {// Firefox
        event.returnValue = false;
        if (event.target.className === 'ace_content') {
          me.changeEditorFont(event.wheelDelta > 0);
        }
      }
    });
  }

  /**
   * drop after drag the resize bar
   */
  listenMouseUp() {
    const me = this;
    me.mouseupListener = me.renderer2.listen('document', 'mouseup', function () {
      if (me.documentEventListener) {
        me.documentEventListener();
        me.documentEventListener = undefined;
      }
      me.resizeEditor();
    });
  }

  /**
   * save code
   * @param tab
   * @param callback
   */
  saveFile(tab: Tab, callback?) {
    if (!tab.isValueChange) {
      return;
    }
    if (tab.id) {
      this.saveToFurther(tab, callback);
    } else {
      this.createFileToFurther(tab, callback);
    }
    tab.isValueChange = false;
    tab.historyCode = tab.code;
    this.menuTreeComponent.afterContentChange(tab);
  }

  /**
   * save to further
   * @param tab
   * @param callback
   */
  saveToFurther(tab: Tab, callback?) {
    const data = {
      path: tab.path,
      content: tab.code,
      id: tab.id,
      codeType: tab.codeType
    };
    this.ideService.updateFileForFurther(data).subscribe(res => {
      // update local furtherMenu
      const furtherMenu = JSON.parse(localStorage.getItem(Setting.storage.furtherMenu)) || [];
      const newFurtherMenu = furtherMenu.map(item => {
        if (item.id === tab.id) {
          item = res;
        }
        return item;
      });
      localStorage.setItem(Setting.storage.furtherMenu, JSON.stringify(newFurtherMenu));
      if (callback && typeof callback === 'function') {
        callback();
      } // 执行回调
    });
  }

  /**
   * create new file to further
   * @param tab
   * @param callback ( the event after files saved successful )
   */
  createFileToFurther(tab: Tab, callback?) {
    const requestData = {
      path: tab.path,
      codeType: tab.codeType,
      content: tab.code
    };
    this.ideService.createFileToFurther(requestData).subscribe((res: any) => {
      tab.id = res.id;
      // 1.update local furtherMenu
      const furtherMenu = JSON.parse(localStorage.getItem(Setting.storage.furtherMenu)) || [];
      furtherMenu.push(res);
      localStorage.setItem(Setting.storage.furtherMenu, JSON.stringify(furtherMenu));

      // 2. update webide menu
      this.menuTreeComponent.afterContentChange(tab);
      if (callback && typeof callback === 'function') {
        callback();
      }
    });
  }

  /**
   * save all files
   */
  saveFiles(callback?) {
    const unsavedFiles = this.editorTabs.filter(item => item.isValueChange);
    if ((!unsavedFiles || unsavedFiles.length === 0) && typeof callback === 'function') {
      callback();
    }
    unsavedFiles.forEach((item, index) => {
      if (item.isValueChange) {
        if (index === unsavedFiles.length - 1) {
          this.saveFile(item, callback);
        } else {
          this.saveFile(item, callback);
        }
      }
    });
  }

  showBindDevice() {
    if (localStorage.getItem(Setting.storage.deviceData)) { return; }
    this.bindDevice = new Date(); // Pass the current date to the child component so it can be fetched each time a device needs to be bound
  }

  // resize editor
  resizeEditor() {
    if (this.aceEditor && this.aceEditor.directiveRef) {
      this.aceEditor.directiveRef.ace().resize();
    }
  }

  addFile() {
    this.addMenu = 'file';
  }

  addFolder() {
    this.addMenu = 'folder';
  }

  /**
   * run code
   * 1. check if has bind device
   * 2. save files to further before send files
   */
  runCode() {
    this.hasBindDevice = Boolean(localStorage.getItem(Setting.storage.deviceData));
    if (!this.hasBindDevice) {
      this.showBindDevice();
      return;
    }
    this.saveFiles(_ => {
      this.sendFileToServer();
    });
  }

  sendFileToServer() {
    let curOpenFile: Tab;
    if (this.editorTabs && this.editorTabs.length > 0) {
      curOpenFile = this.editorTabs[this.selectedEditorTabIndex];
    }
    if (!curOpenFile) {
      this.message.warning(`${Setting.I18nData['Ide0055']}`);
      return;
    }
    const projectMenuArr = this.prepareFiles();
    this.sendFile(projectMenuArr, curOpenFile.path);
  }

  /**
   * get files form local storage saved before
   * if the file is blockly workspace, then the code object content is the pythonCode, so that the file can be run
   */
  prepareFiles(): any[] {
    let projectMenuArr: any[] = [];
    const projectMenuStr = localStorage.getItem(Setting.storage.furtherMenu);
    if (projectMenuStr) {
      projectMenuArr = JSON.parse(projectMenuStr);
    }
    projectMenuArr.forEach(item => {
      if (item.codeType === CodeType.blocklyPython3) {
        this.editorTabs.forEach(tab => {
          if (tab.path === item.path) {
            if (tab.pythonCode) {
              item.content = tab.pythonCode;
            }
          }
        });
      }
    });
    return projectMenuArr;
  }

  /**
   * send files to server
   * @param projectMenuArr  (project file list)
   * @param path ( path of the file to run )
   */
  sendFile(projectMenuArr: any[], path: string) {
    this.ideService.projectStart(projectMenuArr).subscribe((res) => {
      if (res && res.success) {
        const data = {
          cmd: 'start',
          projectVersionId: Setting.projectVersionId,
          data: path
        };
        this.connectWs(data, () => {
          this.isRunningCode = true;
        });
      } else {
        this.isRunningCode = false;
      }
    }, () => {
      this.isRunningCode = false;
    });
  }

  /**
   * pause code,
   */
  pauseCode() {
    const data = {cmd: 'stop', projectVersionId: Setting.projectVersionId};
    this.connectWs(data);
  }

  /**
   * pause code after connection closed
   * @param data
   */
  onConnectClose(data) {
    if (this.isRunningCode) {
      this.pauseCode();
    }
  }

  /**
   * File execution completed
   * @param data
   */
  onFileExecuteComplete(data) {
    if (data) {
      if (data.finish && this.isRunningCode) {
        this.message.info(`${Setting.I18nData['Ide0056']}`);
      }
      this.isRunningCode = false;
    }
  }

  cleanLogs() {
    this.terminal.xtermClear();
  }

  connectWs(data, callback?) {
    this.terminal.localWebsocket(data, callback);
  }

  resetAddMenu() {
    this.addMenu = '';
  }

  /**
   * When a file in the directory is renamed
   */
  onRenameMenu(data) {
    // there will be an array changedFiles after rename menu
    this.editorTabs.forEach(item => {
      if (item.path === data.path) {
        item.title = data.name;
        item.path = data.newPath;
      }
    });
    this.saveEditorTabs();
    this.changeCurActivePath();
  }

  changeCurActivePath() {
    const curActivePath = this.editorTabs[this.selectedEditorTabIndex];
    if (curActivePath) {
      this.curFilePath = curActivePath.path;
    }
  }

  /**
   * When a file/folder in the directory is deleted, tick the opened TAB in the TAB
   * The currently open TAB is the TAB of the previously opened file, or the last TAB
   */
  onDeleteMenu(data) {
    this.editorTabs = this.editorTabs.filter(item => item.path.indexOf(data.path) !== 0);
    this.selectedEditorTabIndex = this.editorTabs.findIndex(item => item.path === this.editorTabs[this.selectedEditorTabIndex].path) || this.editorTabs.length - 1;
    this.saveEditorTabs();
    if (localStorage.getItem(data.path)) {
      localStorage.removeItem(data.path);
    }
  }

  createBlockInFurther() {
    const codeId = this.editorTabs[this.selectedEditorTabIndex].id;
    window.open(`${Setting.furtherFrontendUrl}/projects/${this.furtherData.projectId}` +
      `/edit?access_token=${this.ideService.accessToken}&createBlock=${codeId}&lines=${this.selectedLine}`);
  }

  /**
   * mouse event
   * stupid code
   */
  mouseEvent() {
    const me = this;
    const leftResizeBar: any = this.el.nativeElement.querySelector('#left-resize-bar'); // left resize bar
    const rightResizeBar: any = this.el.nativeElement.querySelector('#right-resize-bar'); // right resize bar
    const PanelTOP: any = this.el.nativeElement.querySelector('#PANEL_TOP'); // panel top

    me.renderer2.listen(leftResizeBar, 'mousedown', function () {
      const parentWidth = PanelTOP.offsetWidth;
      const lw = PanelTOP.getBoundingClientRect().left; // The width to the left of the window
      me.documentEventListener = me.renderer2.listen('document', 'mousemove', function (ev: any) {
        const lEvent = ev || event;
        lEvent.cancelBubble = true;
        const LX = lEvent.clientX - lw;
        if (LX >= 4) {
          me.idePanels.panelLeft = LX / parentWidth * 100;
        }
        if (me.isShowRight) {
          me.idePanels.panelCenter = 100 - me.idePanels.panelLeft - me.idePanels.panelRight;
        } else {
          me.idePanels.panelCenter = 100 - me.idePanels.panelLeft;
        }
      });
    });

    me.renderer2.listen(rightResizeBar, 'mousedown', function () {
      const parentWidth = PanelTOP.offsetWidth;
      const lw = PanelTOP.getBoundingClientRect().left; // The width to the left of the window
      me.documentEventListener = me.renderer2.listen('document', 'mousemove', function (ev: any) {
        const rEvent = ev || event;
        const RX = rEvent.clientX - lw;
        me.idePanels.panelRight = 100 - (RX / parentWidth * 100);
        if (me.isShowMenu || me.isShowExamples) {
          me.idePanels.panelCenter = RX / parentWidth * 100 - me.idePanels.panelLeft;
        } else {
          me.idePanels.panelCenter = 100 - me.idePanels.panelRight;
        }
        me.isResizeXterm = (new Date()).getTime();
      });
    });
  }

  /**
   * ctrl s save listener
   * @param tab
   */
  listenSave(tab) {
    const me = this;
    if (me.editorSaveListener) {
      me.editorSaveListener();
    }
    this.editorSaveListener = this.renderer2.listen('document', 'keydown', function (event: any) {
      if (event.keyCode === 83 && event.ctrlKey) {
        event.preventDefault();
        if (event.target.className === 'ace_text-input') {
          setTimeout(_ => {
          }, 1000);
          me.saveFile(tab);
        }
      }
    });
  }

  /**
   * context menu in editor
   */
  listenContextMenu() {
    const me = this;
    if (me.contextMenuListener) {
      me.contextMenuListener();
    }
    this.contextMenuListener = this.renderer2.listen('window', 'contextmenu', (event: any) => {
      if (this.selectedLine && this.furtherData.canEdit) {
        event.preventDefault();
        this.nzDropdownService.create(event, this.contextMenuTemplate);
      }
    });
  }

  /**
   * 禁止F5刷新
   */
  stopF5() {
    const me = this;
    this.renderer2.listen('document', 'keydown', function (event: any) {
      event = window.event || event;
      const keyCode = event.keyCode || event.which;
      if (keyCode === 116 && me.isRunningCode) {
        if (window.event) {// ie
          try {
            event.keyCode = 0;
          } catch (e) {
          }
          event.returnValue = false;
        } else {// firefox
          event.preventDefault();
        }
      }
    });
  }

  stopCtrlS() {
    this.renderer2.listen('document', 'keydown', (event: any) => {
      event = window.event || event;
      const keyCode = event.keyCode || event.which;
      if (keyCode === 83 && event.ctrlKey) {
        event.preventDefault();
      }
    });
  }

  ngOnDestroy(): void {
    if (this.documentEventListener) {
      this.documentEventListener();
    }
    if (this.mouseupListener) {
      this.mouseupListener();
    }
    if (this.editorMouseWheelListener) {
      this.editorMouseWheelListener();
    }
    if (this.editorSaveListener) {
      this.editorSaveListener();
    }
    if (this.logoutButtonListener) {
      this.logoutButtonListener();
    }
    if (this.logSocket) {
      this.logSocket.close();
    }
    if (this.aceEditor) {
      this.aceEditor = undefined;
    }
  }

}
